﻿<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>RegisterCallback</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link href="../css/commands.css" rel="stylesheet" type="text/css">
<link href="../css/print.css" rel="stylesheet" type="text/css" media="print">
</head>
<body>

<h1>RegisterCallback() <span class="small65">[v1.0.47+]</span></h1>

<p>创建一个机器码地址(machine-code address)。当它被调用时，会将调用重定向至脚本中的一个 <a href="../Functions.htm">函数 </a>。</p>

<p class="CommandSyntax">Address := RegisterCallback(&quot;FunctionName&quot; [, Options = &quot;&quot;, ParamCount = FormalCount, EventInfo = Address])</p>
<h3>参数 </h3>
<table border="1" width="100%" cellspacing="0" cellpadding="3" bordercolor="#C0C0C0">
  <tr>
    <td>Address</td>
    <td><span class="CommandSyntax">若成功，RegisterCallback() </span>返回一个数值地址(numeric address)，可以被 <a href="DllCall.htm">DllCall()</a> 或任何有能力调用机器码函数的其他东西调用。若失败，会返回一个空字符串。失败的原因是当 <em>FunctionName</em>: 1)  不存在； 2) <em>ParamCount </em>接受太多或太少的参数；或 3) 接受任何的 <a href="../Functions.htm#ByRef">ByRef 参数</a>.</td>
  </tr>
  <tr>
    <td>FunctionName</td>
    <td>一个 <a href="../Functions.htm">函数</a> 的名字，如果是一个原意的(literal)字符串，必须用引号引起来。只要 <em>Address</em> 被调用，这个函数就会被自动调用。函数还会接收到传给  <em>Address</em> 的参数。</td>
  </tr>
  <tr>
    <td>Options</td>
    <td><p>指定下列中零个或更多个单词。用空格隔开每一个选项（例如 &quot;C Fast&quot;）。</p>
    <p><strong><a name="Fast" id="Fast"></a>Fast</strong> 或 <strong>F</strong>: 避免每次 <em>FunctionName</em> 被调用时创建新 <a href="../misc/Threads.htm">线程</a> 。 这会提升效率，但必须避免调用 <em>Address</em> 的线程发生变化 （例如：当回调函数被新来的消息触发）. 这是因为 <em>FunctionName</em> 被调用时可以为每一个即将运行的线程改变全局设置如 <a href="../misc/ErrorLevel.htm">ErrorLevel</a>, <a href="../Variables.htm#LastError">A_LastError</a>，还有 <a href="../LastFoundWindow.htm">上一次找到的窗口</a>。要获得更多信息，参见 <a href="#Threads">注意事项</a>.</p>
    <p><strong>CDecl</strong> 或 <strong>C </strong>：让 <em>Address</em> 遵守 &quot;C&quot; 调用约定。此选项通常省略，因为标准调用约定更常用于回调函数。</p></td>
  </tr>
  <tr>
    <td>ParamCount</td>
    <td><em>Address</em> 的调用者将传递的参数数量。如果全部省略，默认为 <em>FunctionName</em> <a href="../Functions.htm#define">函数定义</a> 中 强制参数(mandatory parameters) 的数量。无论如何都要确保调用者会确切地传递此数量的参数。</td>
  </tr>
  <tr>
    <td>EventInfo</td>
    <td>一个 0 与 4294967295 之间的整数。只要 <em>FunctionName</em> 是通过这个 <em>Address</em> 被调用的， 它就可以在 <a href="../Variables.htm#EventInfo">A_EventInfo</a> 中看到。当 <em>FunctionName</em> 是通过多个 <em>Address</em> 被调用时这是很有用的。如果省略，默认为 <em>Address</em>。注释：与其他全局设置不同，<a href="../misc/Threads.htm">当前线程</a>的 A_EventInfo 不会被 <a href="#Fast">fast mode</a> 打乱。</td>
  </tr>
</table>

<h3>回调函数的参数</h3>
<p>一个分配了回调地址的 <a href="../Functions.htm">函数</a> 最多可接受 31 个参数。允许有 <a href="../Functions.htm#optional">可选参数</a> ，当函数被多个调用者调用时这是很有用的。</p>
<p>所有 传入参数(incoming parameters) 都必须是一个 0 到 4294967295 之间的整数。如果一个传入参数应该是有符号的，以下例子可以展现任何的负值：</p>
<pre>if wParam &gt; 0x7FFFFFFF
    wParam := -(~wParam) - 1</pre>
<p>如果一个传入参数应该是一个字符串，可以通过复制这个字符串来接收。例如：</p>
<pre><span class="NoIndent">VarSetCapacity(MyString, DllCall(&quot;lstrlen&quot;, UInt, MyParameter))  </span><em>; 如果MyString已经足够大，则不必如此。</em><span class="NoIndent">
DllCall(&quot;lstrcpy&quot;, Str, MyString, UInt, MyParameter)  <em>; 复制字符串到脚本的 MyString 变量。</em>
VarSetCapacity(MyString, -1)  <em>; 更新内部存储的变量长度以反映出新内容。</em></span></pre>
<p>如果一个传入参数是结构体的地址，参照 <a href="DllCall.htm#struct">DllCall 结构体</a> 中的步骤可以展开其内部成员。</p>
<h3>函数应该 <em>返回</em> 什么</h3>
<p>如果函数使用不带参数的 <a href="Return.htm">Return</a>，或者指定了一个空值例如 &quot;&quot; （或者根本不使用 Return）， 0 将被返回给 <em>Address</em> 的调用者。否则，函数应该返回一个 -2147483648 与 4294967295 之间的整数，它将会被返回给 <em>Address</em> 的调用者。</p>
<h3><a name="Threads"></a>快速模式与慢速模式(Fast vs. Slow)</h3>
<p>默认/慢速 模式(default/slow mode) 让函数全新地以设置（比如 <a href="SendMode.htm">SendMode</a> 和 <a href="DetectHiddenWindows.htm">DetectHiddenWindows</a>）的默认值启动。这些默认值可以在 <a href="../Scripts.htm#auto">自动执行部分</a> 中被修改。</p>
<p>相比之下，<a href="#Fast">快速模式(fast mode)</a> 在函数被调用时会从任何即将运行的 <a href="../misc/Threads.htm">线程</a> 上继承全局设置。而且，函数对全局设置的任何修改（包括 <a href="../misc/ErrorLevel.htm">ErrorLevel</a> 和 <a href="../LastFoundWindow.htm">上一次找到的窗口</a>） 都将在 <a href="../misc/Threads.htm">当前线程</a> 中生效。因此，快速模式应该只在确切地知道函数会被哪个线程调用的情况下才去使用。</p>
<p>要避免被自己（或任何其他的线程）中断，一个回调函数可以在第一行使用 <a href="Critical.htm">Critical</a>。然而，当函数因小于 0x312 的消息的到来而被间接调用时，这并不是完全有效的 (增加 Critical 的 <a href="Critical.htm#Interval">interval(时间间隔)</a> 可能有帮助)。而且， <a href="Critical.htm">Critical</a> 不会阻止函数做一些可能引起调用它自己的事，如调用 <a href="PostMessage.htm">SendMessage</a> 或 <a href="DllCall.htm">DllCall</a>。</p>
<h3>内存</h3>
<p>每次使用 RegisterCallback() 会分配少量的内存 (32 个字节加上系统开销)。由于在脚本退出时操作系统会自动释放这段内存，任何分配数量少而<em>固定</em>的回调地址的脚本无须显式地释放内存。相比之下，一个调用 RegisterCallback() 次数 不确定/无限 的脚本应该显式地对任何不再需要的回调地址调用以下函数： <em>DllCall(&quot;GlobalFree&quot;, UInt, Address)</em></p>
<h3>相关命令</h3>
<p><a href="DllCall.htm">DllCall()</a>, <a href="OnMessage.htm">OnMessage()</a>, <a href="OnExit.htm">OnExit</a>, <a href="../misc/Clipboard.htm#OnClipboardChange">OnClipboardChange</a>, <a href="Sort.htm#callback">Sort 的回调函数</a>, <a href="Critical.htm">Critical</a>, <a href="PostMessage.htm">Post/SendMessage</a>, <a href="../Functions.htm">Functions(函数)</a>, <a href="../misc/SendMessageList.htm">Windows 消息列表</a>, <a href="../misc/Threads.htm">Threads(线程)</a></p>
<h3>示例</h3>
<pre class="NoIndent"><em>; 示例：下面是一个可用脚本，显示所有顶层窗口的摘要。</em>

<em>; 为了提升性能和保护内存，只为一个回调地址调用一次 RegisterCallback() ：</em>
if not EnumAddress  <em>; 可以用快速模式，因为它只会被这个线程调用：</em>
    EnumAddress := <strong>RegisterCallback</strong>(&quot;EnumWindowsProc&quot;, &quot;Fast&quot;)

DetectHiddenWindows On  <em>; 由于是快速模式，这个设置也会在回调函数中生效。</em>

<em>; 将控制权交给 EnumWindows()，它会反复调用回调函数：</em>
DllCall(&quot;EnumWindows&quot;, UInt, EnumAddress, UInt, 0)
MsgBox %Output%  <em>; 显示由回调函数收集的信息：</em>
    
EnumWindowsProc(hwnd, lParam)
{
    global Output
    WinGetTitle, title, ahk_id %hwnd%
    WinGetClass, class, ahk_id %hwnd%
    if title
        Output .= &quot;HWND: &quot; . hwnd . &quot;`tTitle: &quot; . title . &quot;`tClass: &quot; . class . &quot;`n&quot;
    return true  <em>; 告诉 EnumWindows() 在所有窗口被列举完之前继续。</em>
}</pre>
<p>&nbsp;</p>
<pre class="NoIndent"><em>; 示例：下面是一个可用脚本，演示如何在脚本中通过</em>
<em>; 重定向一个窗口的 WindowProc(窗口过程函数) 到一个新的 WindowProc 来 subclass(子类化) 一个 GUI 窗口。</em>
<em>; 在这个例子中，文本控件的背景色会被更改成自定义的颜色。</em>

TextBackgroundColor := 0xFFBBBB  <em>; BGR 格式的自定义颜色。</em>
TextBackgroundBrush := DllCall(&quot;CreateSolidBrush&quot;, UInt, TextBackgroundColor)

Gui, Add, Text, HwndMyTextHwnd, 这里有一些带自定义背景色的文字。
Gui +LastFound
GuiHwnd := WinExist()

WindowProcNew := <strong>RegisterCallback</strong>(&quot;WindowProc&quot;, &quot;&quot;  <em>; 指定 &quot;&quot; 以避免为子类化使用快速模式。</em>
    , <strong>4</strong>, MyTextHwnd)  <em>; 当给出 EventInfo 参数时必须精确地指定 ParamCount。</em>
WindowProcOld := DllCall(&quot;SetWindowLong&quot;, UInt, GuiHwnd, Int, -4  <em>; -4 是 GWL_WNDPROC</em>
    , Int, WindowProcNew, <strong>UInt</strong>)  <em>; 返回值必须指定为 UInt 而不是 Int.</em>

Gui Show
return

WindowProc(hwnd, uMsg, wParam, lParam)
{
    Critical
    global TextBackgroundColor, TextBackgroundBrush, WindowProcOld
    if (uMsg = 0x138 &amp;&amp; lParam = A_EventInfo)  <em>; 0x138 是 WM_CTLCOLORSTATIC.</em>
    {
        DllCall(&quot;SetBkColor&quot;, UInt, wParam, UInt, TextBackgroundColor)
        return TextBackgroundBrush  <em>; 返回 HBRUSH 以通知操作系统我们改变了 HDC。</em>
    }
    <em>; 否则（如果上面的代码不返回），将所有不需要处理的消息传递给原先的 WindowProc.</em>
    return DllCall(&quot;CallWindowProcA&quot;, UInt, WindowProcOld, UInt, hwnd, UInt, uMsg, UInt, wParam, UInt, lParam)
}

GuiClose:
ExitApp</pre>

</body>
</html>
